package com.example.novelweb.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.lang.UUID;
import cn.hutool.json.JSON;
import cn.hutool.json.JSONConverter;
import cn.hutool.json.JSONUtil;
import com.alibaba.druid.support.json.JSONUtils;
import com.example.novelweb.common.ErrorCode;
import com.example.novelweb.constant.RedisConstant;
import com.example.novelweb.exception.BusinessException;
import com.example.novelweb.model.entity.User;
import com.example.novelweb.model.vo.NovelVO;
import com.example.novelweb.model.vo.UserVO;
import com.example.novelweb.service.RedisService;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
public class RedisServiceImpl implements RedisService {

  @Resource
  private StringRedisTemplate stringRedisTemplate;

  @Override
  public void setVarifyCode(String account, String code) {
    stringRedisTemplate.opsForValue().set(RedisConstant.KEY_LOGIN_CODE+account,code,
        RedisConstant.TTL_LOGIN_CODE,TimeUnit.MINUTES);

  }

  @Override
  public String getVarifyCode(String account) {
    return stringRedisTemplate.opsForValue()
        .get(RedisConstant.KEY_LOGIN_CODE+account);
  }

  @Override
  public void delVarifyCode(String account) {
    stringRedisTemplate.delete(RedisConstant.KEY_LOGIN_CODE+account);
  }

  @Override
  public String saveUserOrRefreshToken(String account, User user) {
    String token = null;
    Set<String> set = stringRedisTemplate.keys(RedisConstant.KEY_LOGIN_TOKEN+account+"*");
    if (set == null || set.isEmpty()) {
      // gen token
      token = UUID.randomUUID().toString(true);
      // tran userVO to hashmap
      UserVO userVO = BeanUtil.copyProperties(user,UserVO.class);
      Map<String,Object> userMap = BeanUtil.beanToMap(userVO,new HashMap<>(),
          CopyOptions.create()
              .setIgnoreNullValue(true)
              .setFieldValueEditor((fieldName,fieldValue)->fieldValue.toString()));
      // save to redis
      String tokenKey = RedisConstant.KEY_LOGIN_TOKEN+account+":"+token;
      stringRedisTemplate.opsForHash().putAll(tokenKey,userMap);
      stringRedisTemplate.expire(tokenKey,RedisConstant.TTL_LOGIN_TOKEN, TimeUnit.MINUTES);

    } else {
      // refresh token
      for(String key : set) { // 虽然是for,但是set.size() == 1
        token = key.substring(key.lastIndexOf(":")+1);
        stringRedisTemplate.expire(key,RedisConstant.TTL_LOGIN_TOKEN, TimeUnit.MINUTES);
      }
    }
    return token;
  }

  @Override
  public boolean delToken(String token) {
    Set<String> set = stringRedisTemplate.keys(RedisConstant.KEY_LOGIN_TOKEN+"*"+token);
    if (set == null || set.isEmpty()) {
      return false;
    }
    for (String key : set) {
      stringRedisTemplate.delete(key);
    }
    return true;
  }

  @Override
  public boolean updateUser(String token,String field, String value) {
    Set<String> set  =stringRedisTemplate.keys(RedisConstant.KEY_LOGIN_TOKEN + "*" + token);
    if(set==null || set.isEmpty()) {
      return false;
    }
    String key = set.stream().iterator().next();
    stringRedisTemplate.opsForHash().put(key, field, value);
    return true;
  }

  @Override
  public boolean updateUser(String token, UserVO userVO) {
    Set<String> set  =stringRedisTemplate.keys(RedisConstant.KEY_LOGIN_TOKEN + "*" + token);
    if(set==null || set.isEmpty()) {
      return false;
    }
    String key = set.stream().iterator().next();
    //TODO: atomic
    stringRedisTemplate.opsForHash().put(key, "avatar", userVO.getAvatar());
    stringRedisTemplate.opsForHash().put(key, "nickname", userVO.getNickname());
    return true;
  }


  //============================
  @Override
  public void incrNovelCollectNum(Long novelId) {
    stringRedisTemplate.opsForValue().increment(RedisConstant.KEY_NOVEL_COLLECT_NUM+novelId);
  }
  @Override
  public void decrNovelCollectNum(Long novelId) {
    stringRedisTemplate.opsForValue().decrement(RedisConstant.KEY_NOVEL_COLLECT_NUM+novelId);
  }

  //===========================
  @Override
  public void incrCommentLikeNum(Long commentId) {
    stringRedisTemplate.opsForValue().increment(RedisConstant.KEY_COMMENT_LIKE_NUM+commentId);

  }

  @Override
  public void decrCommentLikeNum(Long commentId) {
    stringRedisTemplate.opsForValue().decrement(RedisConstant.KEY_COMMENT_LIKE_NUM+commentId);
  }

  //===========================

  @Override
  public void setCacheTopCollects(List<NovelVO> items) {
    items.forEach(item ->{
      String json = JSONUtil.toJsonStr(item);
      stringRedisTemplate.opsForList()
          .rightPush(RedisConstant.KEY_TOP_COLLECTS,json);
    });
    stringRedisTemplate.expire(RedisConstant.KEY_TOP_COLLECTS,RedisConstant.TTL_TOP_COLLECTS,TimeUnit.MINUTES);
  }

  @Override
  public List<NovelVO> getCacheTopCollects() {
    try {
      List<String> jsonList = stringRedisTemplate.opsForList()
          .range(RedisConstant.KEY_TOP_COLLECTS, 0, -1);

      List<NovelVO> ret =  jsonList.stream()
            .map(json -> JSONUtil.toBean(json, NovelVO.class))
            .collect(Collectors.toList());
      return ret;
    } catch (NullPointerException e) {
      return Collections.emptyList();
    } catch (Exception e) {
      throw new BusinessException(ErrorCode.OPERATION_ERROR,"get cache from redis error!");
    }

  }

  @Override
  public boolean hasCacheTopCollects() {
    return Boolean.TRUE.equals(stringRedisTemplate.hasKey(RedisConstant.KEY_TOP_COLLECTS));
  }

}
